#![allow(dead_code)]
use std::{error::Error, fmt::Display, io::Read, str::FromStr};

/// the type of the chunkdata
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ChunkType {
    value: [u8; 4],
    // None([u8;4]),
    //     //Name  Multiple OK?  Ordering constraints
    // IHDR([u8;4])    ,//No      Must be first
    // PLTE([u8;4])    ,//No      Before IDAT
    // IDAT([u8;4])    ,//Yes     Multiple IDATs must be consecutive
    // IEND([u8;4])    ,//No      Must be last
    // cHRM([u8;4])    ,//No      Before PLTE and IDAT
    // gAMA([u8;4])    ,//No      Before PLTE and IDAT
    // iCCP([u8;4])    ,//No      Before PLTE and IDAT
    // sBIT([u8;4])    ,//No      Before PLTE and IDAT
    // sRGB([u8;4])    ,//No      Before PLTE and IDAT
    // bKGD([u8;4])    ,//No      After PLTE; before IDAT
    // hIST([u8;4])    ,//No      After PLTE; before IDAT
    // tRNS([u8;4])    ,//No      After PLTE; before IDAT
    // pHYs([u8;4])    ,//No      Before IDAT
    // sPLT([u8;4])    ,//Yes     Before IDAT
    // tIME([u8;4])    ,//No      None
    // iTXt([u8;4])    ,//Yes     None
    // tEXt([u8;4])    ,//Yes     None
    // zTXt([u8;4])    ,//Yes     None
    // Unknown([u8;4])
}

impl ChunkType {
    /// convert self to bytes
    pub fn bytes(&self) -> [u8; 4] {
        let mut t = [0_u8; 4];
        let str = self.to_string();
        str.as_bytes()
            .read_exact(&mut t)
            .expect("read to arrary occur error");
        t
    }
    /// determine the chunk type is critical
    pub fn is_critical(&self) -> bool {
        //Ancillary bit: bit 5 of first byte
        // 0 (uppercase) = critical, 1 (lowercase) = ancillary.
        let first_b = self.value.first().unwrap();
        first_b.is_ascii_uppercase()
    }
    /// determine the chunkt type is public
    pub fn is_public(&self) -> bool {
        //Private bit: bit 5 of second byte
        // 0 (uppercase) = public, 1 (lowercase) = private.
        let b_value = self.value.get(1).unwrap();
        b_value.is_ascii_uppercase()
    }
    /// determine the chunk type is can be reserved
    pub fn is_reserved_bit_valid(&self) -> bool {
        //Reserved bit: bit 5 of third byte
        // Must be 0 (uppercase) in files conforming to this version of PNG.
        let b_value = self.value.get(2).unwrap();
        b_value.is_ascii_uppercase()
    }
    /// determine the chunk type can safe to copy
    pub fn is_safe_to_copy(&self) -> bool {
        //Safe-to-copy bit: bit 5 of fourth byte
        //0 (uppercase) = unsafe to copy, 1 (lowercase) = safe to copy.
        let b_value = self.value.get(3).unwrap();
        b_value.is_ascii_lowercase()
    }
    /// Returns true if the reserved byte is valid and all four bytes are represented by the characters A-Z or a-z.
    /// Note that this chunk type should always be valid as it is validated during construction.
    pub fn is_valid(&self) -> bool {
        if self.is_reserved_bit_valid(){
            for b in self.value {
               if !b.is_ascii_alphabetic(){
                return false;
               }
            }
           return  true;
        }
        false
    }
    
}
impl Display for ChunkType {
    /// impl Display
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", String::from_utf8_lossy(&self.value))
    }
}
impl From<String> for ChunkType {
    fn from(value: String) -> Self {
        value.as_str().into()
    }
}
impl From<&str> for ChunkType {
    fn from(value: &str) -> Self {
        assert_eq!(value.chars().count(), 4);
        Self {
            value: value.as_bytes().try_into().unwrap(),
        }
    }
}

impl From<[u8; 4]> for ChunkType {
    fn from(value: [u8; 4]) -> Self {
        let str = String::from_utf8(value.to_vec()).expect("value can't convert to string");
        str.into()
    }
}
impl FromStr for ChunkType {
    type Err = Box<dyn Error>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
       let has_any_not_alp= s.chars().any(|c|{
            !c.is_ascii_alphabetic()
        });
        if has_any_not_alp{
           return Err("not all chars is alphabetic!".to_string().into());
        }
        match s.as_bytes().try_into() {
            Ok(value) => Ok(Self { value }),
            Err(err) => Err(err.into()),
        }
    }
}


#[cfg(test)]
mod tests {
    use super::*;
    use std::convert::TryFrom;
    use std::str::FromStr;

    #[test]
    pub fn test_chunk_type_from_bytes() {
        let expected = [82, 117, 83, 116];
        let actual = ChunkType::try_from([82, 117, 83, 116]).unwrap();

        assert_eq!(expected, actual.bytes());
    }

    #[test]
    pub fn test_chunk_type_from_str() {
        let expected = ChunkType::try_from([82, 117, 83, 116]).unwrap();
        let actual = ChunkType::try_from("RuSt").unwrap();
        assert_eq!(expected, actual);
    }

    #[test]
    pub fn test_chunk_type_is_critical() {
        let chunk = ChunkType::from_str("RuSt").unwrap();
        assert!(chunk.is_critical());
    }

    #[test]
    pub fn test_chunk_type_is_not_critical() {
        let chunk = ChunkType::from_str("ruSt").unwrap();
        assert!(!chunk.is_critical());
    }

    #[test]
    pub fn test_chunk_type_is_public() {
        let chunk = ChunkType::from_str("RUSt").unwrap();
        assert!(chunk.is_public());
    }

    #[test]
    pub fn test_chunk_type_is_not_public() {
        let chunk = ChunkType::from_str("RuSt").unwrap();
        assert!(!chunk.is_public());
    }

    #[test]
    pub fn test_chunk_type_is_reserved_bit_valid() {
        let chunk = ChunkType::from_str("RuSt").unwrap();
        assert!(chunk.is_reserved_bit_valid());
    }

    #[test]
    pub fn test_chunk_type_is_reserved_bit_invalid() {
        let chunk = ChunkType::from_str("Rust").unwrap();
        assert!(!chunk.is_reserved_bit_valid());
    }

    #[test]
    pub fn test_chunk_type_is_safe_to_copy() {
        let chunk = ChunkType::from_str("RuSt").unwrap();
        assert!(chunk.is_safe_to_copy());
    }

    #[test]
    pub fn test_chunk_type_is_unsafe_to_copy() {
        let chunk = ChunkType::from_str("RuST").unwrap();
        assert!(!chunk.is_safe_to_copy());
    }

    #[test]
    pub fn test_valid_chunk_is_valid() {
        let chunk = ChunkType::from_str("RuSt").unwrap();
        assert!(chunk.is_valid());
    }

    #[test]
    pub fn test_invalid_chunk_is_valid() {
        let chunk = ChunkType::from_str("Rust").unwrap();
        assert!(!chunk.is_valid());

        let chunk = ChunkType::from_str("Ru1t");
        assert!(chunk.is_err());
    }

    #[test]
    pub fn test_chunk_type_string() {
        let chunk = ChunkType::from_str("RuSt").unwrap();
        assert_eq!(&chunk.to_string(), "RuSt");
    }

    #[test]
    pub fn test_chunk_type_trait_impls() {
        let chunk_type_1: ChunkType = TryFrom::try_from([82, 117, 83, 116]).unwrap();
        let chunk_type_2: ChunkType = FromStr::from_str("RuSt").unwrap();
        let _chunk_string = format!("{}", chunk_type_1);
        let _are_chunks_equal = chunk_type_1 == chunk_type_2;
    }
}
